package com.kamistoat.meimeimall.meimeimallorder.service.impl;

import com.alibaba.fastjson.JSON;
import com.kamistoat.meimeimall.common.To.memberTo.LoginSuccessTo;
import com.kamistoat.meimeimall.common.To.RabbitMQTo.OrderTo;
import com.kamistoat.meimeimall.common.To.RabbitMQTo.SecKillOrderTo;
import com.kamistoat.meimeimall.common.To.orderTo.OrderItemNewTo;
import com.kamistoat.meimeimall.common.To.paymentTo.PayAsyncTo;
import com.kamistoat.meimeimall.common.To.paymentTo.PayTo;
import com.kamistoat.meimeimall.common.To.seckillTo.SecKillProductCacheTo;
import com.kamistoat.meimeimall.common.To.seckillTo.SecKillProductTo;
import com.kamistoat.meimeimall.common.To.seckillTo.SecKillSessionTo;
import com.kamistoat.meimeimall.common.To.seckillTo.SecKillSkuInfoTo;
import com.kamistoat.meimeimall.common.To.memberTo.MemberReceiveAddressTo;
import com.kamistoat.meimeimall.common.To.orderTo.OrderItemTo;
import com.kamistoat.meimeimall.common.To.productTo.SpuInfoTo;
import com.kamistoat.meimeimall.common.To.wareTo.HasStockMapTo;
import com.kamistoat.meimeimall.common.To.wareTo.LockOrderItemWareTo;
import com.kamistoat.meimeimall.common.To.wareTo.LockOrderWareTo;
import com.kamistoat.meimeimall.common.annotation.ClearCache;
import com.kamistoat.meimeimall.common.annotation.WithCache;
import com.kamistoat.meimeimall.common.constant.AuthConstant;
import com.kamistoat.meimeimall.common.constant.DatabaseConstant;
import com.kamistoat.meimeimall.common.constant.OrderConstant;
import com.kamistoat.meimeimall.common.constant.SecKillConstant;
import com.kamistoat.meimeimall.common.dubboInterface.*;
import com.kamistoat.meimeimall.common.exception.BizCodeEnum;
import com.kamistoat.meimeimall.common.exception.RRException;
import com.kamistoat.meimeimall.common.snGenerater.SnWorker;
import com.kamistoat.meimeimall.common.threadlocal.ThreadLocalContextHolder;
import com.kamistoat.meimeimall.common.utils.R;
import com.kamistoat.meimeimall.common.utils.SNGenerate;
import com.kamistoat.meimeimall.meimeimallorder.dao.OrderItemDao;
import com.kamistoat.meimeimall.meimeimallorder.dao.OrderSubOrderRelationDao;
import com.kamistoat.meimeimall.meimeimallorder.dao.SubOrderDao;
import com.kamistoat.meimeimall.meimeimallorder.enums.CacheOrderStatusEnum;
import com.kamistoat.meimeimall.meimeimallorder.localTCC.OrderServiceLocalTCC;
import com.kamistoat.meimeimall.meimeimallorder.vo.OrderCreateVo;
import com.kamistoat.meimeimall.meimeimallorder.entity.*;
import com.kamistoat.meimeimall.meimeimallorder.enums.OrderStatusEnum;
import com.kamistoat.meimeimall.meimeimallorder.service.*;
import com.kamistoat.meimeimall.meimeimallorder.vo.*;
import com.kamistoat.meimeimall.meimeimallorder.dao.OrderDao;
import io.seata.rm.tcc.api.BusinessActionContext;
import org.apache.dubbo.config.annotation.Reference;
import org.redisson.api.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;


@Service("orderService")
public class OrderServiceImpl implements OrderService, OrderServiceLocalTCC {
    protected Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class);

    @Qualifier("orderCreateQueryThreadPoolExecutor")
    @Autowired
    ThreadPoolExecutor orderCreateQueryThreadPoolExecutor;
    @Qualifier("orderCacheDeductThreadPoolExecutor")
    @Autowired
    ThreadPoolExecutor orderCacheDeductThreadPoolExecutor;
    @Qualifier("workCrashThreadPoolExecutor")
    @Autowired
    ThreadPoolExecutor workCrashThreadPoolExecutor;
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    @Qualifier("redisOrderLogTemplate")
    StringRedisTemplate redisOrderLogTemplate;
    @Autowired
    @Qualifier("redisProductTemplate")
    StringRedisTemplate redisProductTemplate;
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    OrderDao orderDao;

    @Autowired
    SubOrderService subOrderService;
    @Autowired
    SubOrderDao subOrderDao;
    @Autowired
    OrderItemDao orderItemDao;
    @Autowired
    OrderSubOrderRelationDao orderSubOrderRelationDao;
    @Autowired
    OrderServiceLocalTCC orderServiceLocalTCC;

    @Reference
    CartDubboInterface cartDubboInterface;
    @Reference
    CouponDubboInterface couponDubboInterface;
    @Reference
    MemberDubboInterface memberDubboInterface;
    @Reference
    ProductDubboInterface productDubboInterface;
    @Reference
    WareDubboInterface wareDubboInterface;

    /**
     * 当用户点击购物车结算时，构建订单检查页数据。
     * 此处完全由后端重新构建，包括选中商品、商品价格、库存情况、收货地址，而非相信前端。
     * 该方法数据构建应尽可能从缓存中提取
     *
     * @return 订单检查页数据
     */
    @Override
    public OrderCheckVo checkOrder() {
        OrderCheckVo orderCheckVo = new OrderCheckVo();
        // 先从ThreadLocal中把用户信息拿出来
        LoginSuccessTo loginSuccessTo = JSON.parseObject((String) ThreadLocalContextHolder.get(AuthConstant.LOGIN_USER_THREADLOCAL_NAME), LoginSuccessTo.class);
        // 把主线程中的上下文信息提取出来，在异步Future之间进行同步
        RequestAttributes mainThreadContext = RequestContextHolder.getRequestAttributes();

        try {
            /**
             * 异步查询-member模块-收货地址带缓查询
             */
            CompletableFuture<Void> getOrderAddressAsync = CompletableFuture.runAsync(() -> {
                // 首先强制将异步线程的数据和主线程数据同步。注意，这个RequestContextHolder和上面主线程中的RequestContextHolder已经不是同一个了
                RequestContextHolder.setRequestAttributes(mainThreadContext);

                // RPC结果默认为空列表
                List<MemberReceiveAddressTo> memberReceiveAddressToList = new ArrayList<>();

                // Dubbo调用
                R orderAddressR = memberDubboInterface.getOrderAddress(loginSuccessTo.getId());
                if (orderAddressR.getCode() != 0) {
                    logger.error(orderAddressR.getMsg());
                }
                orderCheckVo.setMemberAddressVoList(memberReceiveAddressToList);
            }, orderCreateQueryThreadPoolExecutor);

            /**
             * 异步查询-cart模块-带缓查询购物车中选中商品
             */
            CompletableFuture<Void> getOrderCartCheckedAsync = CompletableFuture.runAsync(() -> {
                // 强制将异步线程的数据和主线程数据同步。注意，这个RequestContextHolder和上面主线程中的RequestContextHolder已经不是同一个了
                RequestContextHolder.setRequestAttributes(mainThreadContext);

                // 远程调用接口查询用户购物车中被选中的购物项
                List<OrderItemTo> orderItemToList = new ArrayList<>();

                // Dubbo 调用
                R orderCartCheckedR = cartDubboInterface.getOrderCartChecked();
                if (orderCartCheckedR.getCode() != 0) {
                    logger.error(orderCartCheckedR.getMsg());
                } else {
                    orderItemToList = JSON.parseArray((String) orderCartCheckedR.get("orderItemToList"), OrderItemTo.class);
                }
                orderCheckVo.setOrderItemVoList(orderItemToList);
            }, orderCreateQueryThreadPoolExecutor).thenRunAsync(() -> {
                /**
                 * 异步查询-ware模块-带缓查询商品库存情况
                 */
                // 紧接着查询所有商品的库存情况
                List<OrderItemTo> orderItemToList = orderCheckVo.getOrderItemVoList();
                List<Long> idList = orderItemToList.stream().map(OrderItemTo::getSkuId).collect(Collectors.toList());

                // Dubbo调用
                R hasStockR = wareDubboInterface.hasStock(idList);
                if (hasStockR.getCode() != 0) {
                    logger.error(hasStockR.getMsg());
                } else {
                    HasStockMapTo hasStockMapTo = JSON.parseObject((String) hasStockR.get("hasStockMapTo"), HasStockMapTo.class);
                    Map<Long, Boolean> hasStockMap = hasStockMapTo.getHasStockMap();
                    orderCheckVo.setHasStockMap(hasStockMap);
                }
            }, orderCreateQueryThreadPoolExecutor);

            /**
             * 异步动作，设置防重令牌。
             * 每一个订单检查页都应该设置防重令牌，令牌缓存在redis中。令牌的作用有两点:
             * 1. 用户在订单检查页停留超长时间后提交支付请求，此时redis中的令牌已过期，应直接拒绝.
             * 2. 用户在订单检查页重复提交，则令牌重复，也应该直接拒绝.
             * 当然，后续的订单检查页提交本身就从数据库重新构建，上面两点是可以在重新构建时保证一致性的，防重令牌属于是提前减少数据库冲击。
             */
            CompletableFuture<Void> setOrderTokenAsync = CompletableFuture.runAsync(() -> {
                String uniqueToken = UUID.randomUUID().toString().replace("-", "");
                orderCheckVo.setOrderUniqueToken(uniqueToken);
                // 将防重令牌保存到redis中，过期时间为10分钟
                stringRedisTemplate.opsForValue().set(
                        OrderConstant.ORDER_UNIQUE_TOKEN_PREFIX + loginSuccessTo.getId(),
                        uniqueToken,
                        OrderConstant.ORDER_UNIQUE_TOKEN_EXPIRE,
                        OrderConstant.ORDER_UNIQUE_TOKEN_EXPIRE_UNIT);
            }, orderCreateQueryThreadPoolExecutor);

            // 用户积分，直接从ThreadLocal中拿出来即可
            orderCheckVo.setIntegration(loginSuccessTo.getIntegration());

            /**
             * 等待所有异步任务完成
             */
            CompletableFuture.allOf(getOrderAddressAsync, getOrderCartCheckedAsync, setOrderTokenAsync).get();

            return orderCheckVo;
        } catch (Exception e) {
            logger.error(e.getMessage());
            throw new RRException(e.getMessage(), BizCodeEnum.CONSTRUCT_CHECK_FAIL.getCode());
        }
    }

    /**
     * 秒杀商品直接点击 立刻购买时，构建订单检查页。
     * 不会操作信号量锁，只会尽力从缓存中查询数据构建订单检查页。
     *
     * @return
     */
    @Override
    public OrderCheckVo checkSeckillOrder(Long sessionId, String randomCode, Long skuId, Integer wantNum) {
        OrderCheckVo orderCheckVo = new OrderCheckVo();
        // 声明这是秒杀订单
        orderCheckVo.setIsSeckill(true);
        // 先从ThreadLocal中把用户信息拿出来
        LoginSuccessTo loginSuccessTo = JSON.parseObject((String) ThreadLocalContextHolder.get(AuthConstant.LOGIN_USER_THREADLOCAL_NAME), LoginSuccessTo.class);
        // 把主线程中的上下文信息提取出来，在异步任务时强制同步
        RequestAttributes mainThreadContext = RequestContextHolder.getRequestAttributes();

        /**
         * 从缓存预热中查出这个秒杀活动。
         * 由于所有秒杀必须在缓存中预热，所以这里没有击穿风险
         */
        String currentSecKillSessionJSON = stringRedisTemplate.opsForValue().get(SecKillConstant.SEC_SESSION_PREFIX + sessionId + ":*");

        /**
         * 检验秒杀活动是否存在
         */
        if (StringUtils.isEmpty(currentSecKillSessionJSON)) {
            // 预热中不存在该秒杀活动
            throw new RRException(BizCodeEnum.SECKILL_CLOSED.getMessage(), BizCodeEnum.SECKILL_CLOSED.getCode());
        }

        // 当前的秒杀活动
        SecKillSessionTo currentSecKillSessionTo = JSON.parseObject(currentSecKillSessionJSON, SecKillSessionTo.class);
        // 寻找到当前秒杀活动含有的该秒杀商品的详情。
        SecKillProductCacheTo cacheTo = currentSecKillSessionTo.getSecKillProductCacheToList().stream().filter(secKillProductCacheTo -> {
            return secKillProductCacheTo.getSecKillProductTo().getSkuId().equals(skuId);
        }).collect(Collectors.toList()).get(0);

        /**
         * 检验商品是否为当前秒杀活动的商品
         */
        if (!cacheTo.getSecKillProductTo().getSkuId().equals(skuId)) {
            // 合法性校验失败。skuId对不上
            throw new RRException(BizCodeEnum.SECKILL_VERIFY_NOT_PRODUCT.getMessage(), BizCodeEnum.SECKILL_VERIFY_NOT_PRODUCT.getCode());
        }

        /**
         * 检验秒杀活动是否开始
         */
        Long startTime = cacheTo.getStartTime();
        Long endTime = cacheTo.getEndTime();
        long curtime = new Date().getTime();
        if (curtime < startTime || curtime > endTime) {
            // 合法性校验失败。时间不对
            throw new RRException(BizCodeEnum.SECKILL_VERIFY_NOT_BEGIN.getMessage(), BizCodeEnum.SECKILL_VERIFY_NOT_BEGIN.getCode());
        }

        /**
         * 检验秒杀随机码
         */
        if (!cacheTo.getRandomCode().equals(randomCode)) {
            // 合法性校验失败，随机码不对
            throw new RRException(BizCodeEnum.SECKILL_VERIFY_NOT_TOKEN.getMessage(), BizCodeEnum.SECKILL_VERIFY_NOT_TOKEN.getCode());
        }

        /**
         * 检验购买数量
         */
        if (cacheTo.getSecKillProductTo().getSeckillOnceLimit() <= wantNum) {
            // 合法性校验失败，单次购买超过限制
            throw new RRException(BizCodeEnum.SECKILL_VERIFY_TOTAL_OVER.getMessage(), BizCodeEnum.SECKILL_VERIFY_TOTAL_OVER.getCode());
        }

        /**
         * 验证当前用户累计购买是否超过限制
         */
        String redisKey = SecKillConstant.SEC_USER_PRODUCT_COUNT + sessionId.toString() + "_" + loginSuccessTo.getId().toString();
        Long execute = stringRedisTemplate.execute(
                new DefaultRedisScript<>("if (tonumber(redis.call('get', KEYS[1])) + tonumber(ARGV[1]) > tonumber(ARGV[2])) then " +
                        "return 1; " +
                        "end; " +
                        "return 0;", Long.class)
                , Collections.singletonList(redisKey), wantNum, cacheTo.getSecKillProductTo().getSeckillUserLimit());
        if (execute.equals(1L)) {
            throw new RRException(BizCodeEnum.SECKILL_VERIFY_SELF_OVER.getMessage(), BizCodeEnum.SECKILL_VERIFY_SELF_OVER.getCode());
        }

        try {
            /**
             * 秒杀参数全部校验成功后，开始构建秒杀商品的订单检查页。
             * 异步查询-member模块-带缓查询收货地址列表
             */
            CompletableFuture<Void> getOrderAddressAsync = CompletableFuture.runAsync(() -> {
                // 首先强制将异步线程的数据和主线程数据同步。注意，这个RequestContextHolder和上面主线程中的RequestContextHolder已经不是同一个了
                RequestContextHolder.setRequestAttributes(mainThreadContext);

                // RPC结果默认为空列表
                List<MemberReceiveAddressTo> memberReceiveAddressToList = new ArrayList<>();

                // Dubbo调用
                R orderAddressR = memberDubboInterface.getOrderAddress(loginSuccessTo.getId());
                if (orderAddressR.getCode() != 0) {
                    logger.error(orderAddressR.getMsg());
                }
                orderCheckVo.setMemberAddressVoList(memberReceiveAddressToList);
            }, orderCreateQueryThreadPoolExecutor);

            /**
             * 异步查询-直接根据秒杀商品在缓存中的预热信息，填充商品信息。不用再去数据库查询了
             */
            CompletableFuture<Void> buildSkuInfoAsync = CompletableFuture.runAsync(() -> {
                OrderItemTo orderItemTo = new OrderItemTo();
                SecKillProductTo secKillProductInfo = cacheTo.getSecKillProductTo();
                SecKillSkuInfoTo secKillOriInfo = cacheTo.getSecKillSkuInfoTo();
                // 保存订单小项详情
                orderItemTo.setSkuId(secKillOriInfo.getSkuId());
                orderItemTo.setTitle(secKillOriInfo.getSkuTitle());
                orderItemTo.setImage(secKillOriInfo.getSkuDefaultImg());
                orderItemTo.setSkuAttr(secKillOriInfo.getSkuAttr());
                // 商品价格使用秒杀价格
                orderItemTo.setPrice(secKillProductInfo.getSeckillPrice());
                orderItemTo.setCount(wantNum);
                orderItemTo.setHasStock(true);
                orderItemTo.setBrandId(secKillOriInfo.getBrandId());
                orderItemTo.setBrandName(secKillOriInfo.getBrandName());
                // 写进对象
                orderCheckVo.setOrderItemVoList(Collections.singletonList(orderItemTo));
            }, orderCreateQueryThreadPoolExecutor);

            /**
             * 异步查询-直接根据秒杀商品的信号量锁判断是否有货，而不是去数据库查
             */
            CompletableFuture<Void> utilsInfoAsync = CompletableFuture.runAsync(() -> {
                // 直接根据信号量锁余量判断是否有货
                RSemaphore semaphore = redissonClient.getSemaphore(SecKillConstant.SEC_PRODUCT_SEMAPHORE + randomCode);
                Map<Long, Boolean> hasStock = new HashMap<>();
                hasStock.put(skuId, semaphore.availablePermits() > 0);
                orderCheckVo.setHasStockMap(hasStock);
                // 用户积分，直接从ThreadLocal中拿出来即可
                orderCheckVo.setIntegration(loginSuccessTo.getIntegration());
                // 保存秒杀相关参数，如果后续还需要合法性校验的话可以使用
                orderCheckVo.setSessionProId(sessionId);
                orderCheckVo.setRandomCode(randomCode);
            }, orderCreateQueryThreadPoolExecutor);

            /**
             * 异步动作，设置防重令牌。
             */
            CompletableFuture<Void> setOrderTokenAsync = CompletableFuture.runAsync(() -> {
                String uniqueToken = UUID.randomUUID().toString().replace("-", "");
                orderCheckVo.setOrderUniqueToken(uniqueToken);
                // 将防重令牌保存到redis中，过期时间为10分钟
                stringRedisTemplate.opsForValue().set(
                        OrderConstant.ORDER_UNIQUE_TOKEN_PREFIX + loginSuccessTo.getId(),
                        uniqueToken,
                        OrderConstant.ORDER_UNIQUE_TOKEN_EXPIRE,
                        OrderConstant.ORDER_UNIQUE_TOKEN_EXPIRE_UNIT);
            }, orderCreateQueryThreadPoolExecutor);

            CompletableFuture.allOf(getOrderAddressAsync, buildSkuInfoAsync, utilsInfoAsync, setOrderTokenAsync).get();

            return orderCheckVo;
        } catch (Exception e) {
            logger.error(e.getMessage());
            throw new RRException(e.getMessage(), BizCodeEnum.CONSTRUCT_CHECK_FAIL.getCode());
        }
    }


    /**
     * 在订单检查页点击去支付，执行真实的下单动作。
     * 该方法只负责做订单的检查通过后就提交下单消息给消息队列.
     * 普通订单: 防重令牌检查 + 固定订单价格(什么时候下单，用什么时候的价格，而不是消息队列处理到的时候)
     * 秒杀订单: 防重令牌检查 + 秒杀参数校验 + 信号量检验(不扣减) + 携带秒杀详细信息(防止下单消息被消费时，秒杀已经过期而查不到相信信息)
     *
     * @param orderSubmitVo 下单请求对象
     * @return
     */
    @Override
    public void submitOrder(OrderSubmitVo orderSubmitVo) {
        LoginSuccessTo loginSuccessTo = JSON.parseObject((String) ThreadLocalContextHolder.get(AuthConstant.LOGIN_USER_THREADLOCAL_NAME), LoginSuccessTo.class);
        orderSubmitVo.setUserId(loginSuccessTo.getId());
        /**
         * 验证令牌. 两种订单都得做.
         */
        // 订单检查页提交的防重令牌必须存在与redis中，否则说明要么防重令牌已过期，要么说明防重令牌已经提交过.
        // 其核心是 -- 令牌的验证和删除必须保持原子性.
        // 使用LUA脚本来执行，从而保证redis的原子性操作，而不是使用手动的redisTemplates去操作
        // 这个脚本的返回结果是Long类型的0和1。0：当KEYS[1]不存在 或 删除KEYS[1]失败。1：KEYS[1]存在且和ARGV[1]相等并成功删除
        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        String orderUniqueToken = orderSubmitVo.getOrderUniqueToken();
        // 第二个参数就是输入的KEYS数组，必须是数组
        // 之后的所有参数共同组成ARGV数组，每个参数代表ARGV数组中的第几个元素
        Long tokenResult = stringRedisTemplate.execute(
                new DefaultRedisScript<Long>(luaScript, Long.class),
                Collections.singletonList(OrderConstant.ORDER_UNIQUE_TOKEN_PREFIX + loginSuccessTo.getId()),
                orderUniqueToken);
        if (tokenResult == null || tokenResult == 0L) {
            // 令牌验证失败. 要么令牌已失效，要么删除令牌失败(一般不会出现这种情况)
            throw new RRException(BizCodeEnum.INVALID_ORDER_UNIQUE_TOKEN.getMessage(), BizCodeEnum.INVALID_ORDER_UNIQUE_TOKEN.getCode());
        }

        int userCount = 0;
        String userCountKey = "";

        /**
         * 如果是秒杀订单，还需要校验秒杀参数，并携带上秒杀详细信息(防止下单消息被消费时，秒杀已经过期而查不到相信信息)
         */
        if (orderSubmitVo.getIsSeckill()) {
            /**
             * 从缓存预热中查出这个秒杀活动。
             * 由于所有秒杀必须在缓存中预热，所以这里没有击穿风险
             */
            String currentSecKillSessionJSON = stringRedisTemplate.opsForValue().get(SecKillConstant.SEC_SESSION_PREFIX + orderSubmitVo.getSeckillSessionId() + ":*");

            /**
             * 检验秒杀活动是否存在
             */
            if (StringUtils.isEmpty(currentSecKillSessionJSON)) {
                // 预热中不存在该秒杀活动
                throw new RRException(BizCodeEnum.SECKILL_CLOSED.getMessage(), BizCodeEnum.SECKILL_CLOSED.getCode());
            }

            // 当前的秒杀活动
            SecKillSessionTo currentSecKillSessionTo = JSON.parseObject(currentSecKillSessionJSON, SecKillSessionTo.class);
            // 寻找到当前秒杀活动含有的该秒杀商品的详情。
            SecKillProductCacheTo cacheTo = currentSecKillSessionTo.getSecKillProductCacheToList().stream().filter(secKillProductCacheTo -> {
                return secKillProductCacheTo.getSecKillProductTo().getSkuId().equals(orderSubmitVo.getSeckillSkuId());
            }).collect(Collectors.toList()).get(0);


            /**
             * 检验商品是否为当前秒杀活动的商品
             */
            if (!cacheTo.getSecKillProductTo().getSkuId().equals(orderSubmitVo.getSeckillSkuId())) {
                // 合法性校验失败。skuId对不上
                throw new RRException(BizCodeEnum.SECKILL_VERIFY_NOT_PRODUCT.getMessage(), BizCodeEnum.SECKILL_VERIFY_NOT_PRODUCT.getCode());
            }

            /**
             * 检验秒杀活动是否开始
             */
            Long startTime = cacheTo.getStartTime();
            Long endTime = cacheTo.getEndTime();
            long curtime = new Date().getTime();
            if (curtime < startTime || curtime > endTime) {
                // 合法性校验失败。时间不对
                throw new RRException(BizCodeEnum.SECKILL_VERIFY_NOT_BEGIN.getMessage(), BizCodeEnum.SECKILL_VERIFY_NOT_BEGIN.getCode());
            }

            /**
             * 检验秒杀随机码
             */
            if (!cacheTo.getRandomCode().equals(orderSubmitVo.getSeckillRandomCode())) {
                // 合法性校验失败，随机码不对
                throw new RRException(BizCodeEnum.SECKILL_VERIFY_NOT_TOKEN.getMessage(), BizCodeEnum.SECKILL_VERIFY_NOT_TOKEN.getCode());
            }

            /**
             * 检验购买数量
             */
            if (cacheTo.getSecKillProductTo().getSeckillOnceLimit() <= orderSubmitVo.getSeckillNum()) {
                // 合法性校验失败，单次购买超过限制
                throw new RRException(BizCodeEnum.SECKILL_VERIFY_TOTAL_OVER.getMessage(), BizCodeEnum.SECKILL_VERIFY_TOTAL_OVER.getCode());
            }

            /**
             * 验证当前用户累计购买是否超过限制
             */
            userCountKey = SecKillConstant.SEC_USER_PRODUCT_COUNT + orderSubmitVo.getSeckillSessionId().toString() + "_" + loginSuccessTo.getId().toString();
            Long execute = stringRedisTemplate.execute(
                    new DefaultRedisScript<>("if (tonumber(redis.call('get', KEYS[1])) + tonumber(ARGV[1]) > tonumber(ARGV[2])) then " +
                            "return 1; " +
                            "end; " +
                            "return 0;", Long.class)
                    , Collections.singletonList(userCountKey), orderSubmitVo.getSeckillNum(), cacheTo.getSecKillProductTo().getSeckillUserLimit());
            if (execute.equals(1L)) {
                throw new RRException(BizCodeEnum.SECKILL_VERIFY_SELF_OVER.getMessage(), BizCodeEnum.SECKILL_VERIFY_SELF_OVER.getCode());
            }

            /**
             * 携带上秒杀详细信息，防止消费时秒杀已过期。
             */
            orderSubmitVo.setSecKillProductCacheTo(cacheTo);
        }

        /**
         * 如果是秒杀订单，同样检验信号量锁余量
         */
        if (orderSubmitVo.getIsSeckill()) {
            RSemaphore semaphore = redissonClient.getSemaphore(SecKillConstant.SEC_PRODUCT_SEMAPHORE + orderSubmitVo.getSeckillRandomCode());
            if (semaphore.availablePermits() <= 0) {
                throw new RRException(BizCodeEnum.SECKILL_VERIFY_NO_SEMAPHORE.getMessage(), BizCodeEnum.SECKILL_VERIFY_NO_SEMAPHORE.getCode());
            }
        }

        /**
         * 固定订单小项列表，下单消息消费者直接使用该小项列表. 两种订单都做
         *
         * 普通订单: 再去数据库根据购物车构建最终下单订单.
         * 秒杀订单: 由于秒杀商品不入购物车，所以直接根据秒杀信息确定商品即可.
         *
         * 此方法仅查询数据库，无修改.
         */
        buildOrderItemToList(orderSubmitVo);

        /**
         * 预处理通过后，直接发送消息，由下游消费者创建订单
         */
        orderSubmitVo.setUserId(loginSuccessTo.getId());
        rabbitTemplate.convertAndSend("order-event-exchange", "order.create.unpay", orderSubmitVo);
    }


    /**
     * 最新实现。
     * 无论是普通商品，还是促销商品，已经全部统一逻辑。
     * 做缓存的WAL预扣减，并发送下单消息.
     * 由于作为所有下单流量的入口，所以最好单独在一个模块进行部署
     */
    @Override
    public void submitOrderForAllType(OrderSubmitVo orderSubmitVo) {
        LoginSuccessTo loginSuccessTo = JSON.parseObject((String) ThreadLocalContextHolder.get(AuthConstant.LOGIN_USER_THREADLOCAL_NAME), LoginSuccessTo.class);
        orderSubmitVo.setUserId(loginSuccessTo.getId());

        /**
         * 1. 验证防重令牌，防止订单重复提交和超时订单提交
         */
        // 订单检查页提交的防重令牌必须存在与redis中，否则说明要么防重令牌已过期，要么说明防重令牌已经提交过.
        // 其核心是 -- 令牌的验证和删除必须保持原子性.
        // 使用LUA脚本来执行，从而保证redis的原子性操作，而不是使用手动的redisTemplates去操作
        // 这个脚本的返回结果是Long类型的0和1。0：当KEYS[1]不存在 或 删除KEYS[1]失败。1：KEYS[1]存在且和ARGV[1]相等并成功删除
        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        String orderUniqueToken = orderSubmitVo.getOrderUniqueToken();
        // 第二个参数就是输入的KEYS数组，必须是数组
        // 之后的所有参数共同组成ARGV数组，每个参数代表ARGV数组中的第几个元素
        Long tokenResult = stringRedisTemplate.execute(
                new DefaultRedisScript<Long>(luaScript, Long.class),
                Collections.singletonList(OrderConstant.ORDER_UNIQUE_TOKEN_PREFIX + loginSuccessTo.getId()),
                orderUniqueToken);
        if (tokenResult == null || tokenResult == 0L) {
            // 令牌验证失败. 要么令牌已失效，要么删除令牌失败(一般不会出现这种情况)
            throw new RRException(BizCodeEnum.INVALID_ORDER_UNIQUE_TOKEN.getMessage(), BizCodeEnum.INVALID_ORDER_UNIQUE_TOKEN.getCode());
        }

        /**
         * 向用户订单日志缓存集群中插入预扣除
         */
        // WAL机制，无论业务实例宕机 / 缓存宕机 都可以保障一致性
        String orderSN = SNGenerate.randomOrderCode();
        orderSubmitVo.setOrderSN(orderSN);
        String logMapPrefix = "order::cacheLog::{" + loginSuccessTo.getId() + "}::" + orderSN;
        BoundHashOperations<String, String, Integer> logMapOps = redisOrderLogTemplate.boundHashOps(logMapPrefix);
        for (OrderItemNewTo orderItemNewTo : orderSubmitVo.getOrderItemNewToList()) {
            logMapOps.put(orderItemNewTo.getSkuId().toString(), (-1 * orderItemNewTo.getCount()));
        }
        logMapOps.put("state", CacheOrderStatusEnum.DEDUCTING.getCode());

        /**
         * 开始对订单中的商品执行预扣除
         */
        List<OrderItemNewTo> successList = new ArrayList<>();
        List<CompletableFuture<Void>> asyncList = new ArrayList<>();
        for (OrderItemNewTo orderItemNewTo : orderSubmitVo.getOrderItemNewToList()) {
            // 异步对每件商品执行操作
            asyncList.add(
                    CompletableFuture.runAsync(() -> {
                        // 由于普通商品和促销商品的缓存对象不一样，缓存内部需要自己做处理
                        // 依靠缓存预热机制，可以确保如果一个商品正处于促销活动中，缓存中一定有他的促销商品对象
                        // 而一个商品如果处于普通状态，缓存中一定只有它的普通商品对象
                        String normalStockPrefix = "product::{" + orderItemNewTo.getSkuId() + "}";
                        String seckillStockPrefix = "seckill::product::{" + orderItemNewTo.getSkuId() + "}";
                        // LUA脚本执行分片缓存扣除，由缓存完成到底是扣的普通商品还是促销商品
                        String deductScript =
                                "local oriStock = 0;" +
                                "if redis.call('exists', KEYS[1])" +
                                    "then" +
                                        "oriStock = redis.call('hget', KEYS[1], 'stock');" +
                                        "if oriStock >= ARGV[1] " +
                                            "then" +
                                                "redis.call('hset', KEYS[1], 'stock', oriStock - ARGV[1]);" +
                                                "return redis.call('hget', KEYS[1], 'seckillSessionId')" +
                                            "else" +
                                                "return -1" +
                                        "end" +
                                    "else" +
                                        "oriStock = redis.call('get', KEYS[2], 'stock');" +
                                        "if oriStock >= ARGV[1] " +
                                            "then" +
                                                "return redis.call('hset', KEYS[2], 'stock', oriStock - ARGV[1]);" +
                                                "return 0" +
                                            "else" +
                                                "return -1" +
                                        "end" +
                                "end";
                        Long deductResult = redisProductTemplate.execute(
                                new DefaultRedisScript<Long>(deductScript, Long.class),
                                Arrays.asList(seckillStockPrefix, normalStockPrefix),
                                orderItemNewTo.getCount());
                        // 返回-1说明扣除失败，可以跳过该商品尽力下单其他商品 / 也可以认为此次下单失败，则根据已预扣除日志进行回滚
                        if (deductResult == null || deductResult == -1L) {
                            logMapOps.delete(orderItemNewTo.getSkuId().toString());
                        }
                        // 返回0说明以普通商品扣除，记录操作日志
                        else if (deductResult == 0L) {
                            logMapOps.put(orderItemNewTo.getSkuId().toString(), orderItemNewTo.getCount());
                            successList.add(orderItemNewTo);
                        }
                        // 返回其他值说明在促销活动中，记录操作日志并固定促销活动
                        else {
                            logMapOps.put(orderItemNewTo.getSkuId().toString(), orderItemNewTo.getCount());
                            orderItemNewTo.setSeckillSessionId(deductResult);
                            successList.add(orderItemNewTo);
                        }
                    }, orderCacheDeductThreadPoolExecutor));

            /**
             * 检查失败商品数量，只要有超过一个成功，就对成功扣除的商品走后续流程
             */
            try {
                CompletableFuture.allOf(asyncList.toArray(new CompletableFuture[0])).get();
                if (successList.size() == 0) {
                    // 所有商品都没库存了，直接返回前端订单失败即可，且该订单不需要入库
                    R createUnpayR = R.error(BizCodeEnum.NO_STOCK_EXCEPTION.getCode(), "该订单中的所有商品都没有充足库存");
                    rabbitTemplate.convertAndSend("order-event-exchange", "order.create.result", createUnpayR);
                    // 删除预扣除日志
                    redisOrderLogTemplate.delete(logMapPrefix);
                } else {
                    logMapOps.put("state", CacheOrderStatusEnum.CREATING.getCode());
                    // 至少有一个商品预扣减成功，则在此处固定订单商品价格，之后的下单全部使用此处固定的价格
                    orderSubmitVo.setOrderItemNewToList(successList);
                    /**
                     * 固定订单商品价格
                     */
                    buildOrderItemToList(orderSubmitVo);
                    /**
                     * 预处理通过后，直接发送消息，由下游消费者创建订单
                     */
                    rabbitTemplate.convertAndSend("order-event-exchange", "order.create.unpay", orderSubmitVo);
                    // 删除日志，因为订单已经完全进入下游，缓存中的日志也没有意义了
                    redisOrderLogTemplate.delete(logMapPrefix);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 业务层宕机的补偿逻辑.
     * 由于缓存做了完善的WAL机制，所以业务层宕机不会影响原子性.
     * 业务层宕机可能导致以下情况:
     * 1. 连日志都还没写完就宕机了
     * 2. 日志成功写入，在执行库存预扣减时宕机，则已经扣除库存的商品出现缓-库不一致
     * 3. 预扣减完成，但是向MQ发送下单消息的前后宕机
     * <p>
     * 最简单的解决方案: 插入预扣减日志时设置过期时间，宕机后日志能自动过期删除，且不做任何的补偿，完全由之后的库存同步线程保证缓存和数据库库存相同即可。
     * 对情况1，最佳方案。对情况3，最佳方案。对情况2，用户需要重新下单
     * <p>
     * 最完善的解决方案: 预扣减日志永久存活，必须手动删除。宕机后启动补偿线程，扫描所有的预扣减日志，根据其state来执行补偿逻辑。
     * 由于业务层已宕机，所以不需要抢锁。
     * 对情况1和情况3的处理没有区别，唯一好处就是对于已经写完预扣除日志的用户，即使业务层宕机也能帮助他们创建完订单。仍然需要之后的同步线程同步库存。
     * 此方案需要使用scan命令，较为耗时。此处给出该方案的实现
     */
    public void compensateForWorkCrash() {
        // 获取日志缓存结群的链接
        RedisConnectionFactory connectionFactory = redisOrderLogTemplate.getConnectionFactory();
        LettuceConnectionFactory clusterConnection = (LettuceConnectionFactory) connectionFactory.getConnection();
        Set<RedisNode> clusterNodes = clusterConnection.getClusterConfiguration().getClusterNodes();
        List<CompletableFuture<Void>> asyncList = new ArrayList<>();
        // 缓存集群中的每一个节点由一个线程进行scan处理
        for (RedisNode redisNode : clusterNodes) {
            asyncList.add(CompletableFuture.runAsync(() -> {
                // 创建操作缓存节点的template
                StringRedisTemplate curTemplate =
                        new StringRedisTemplate(new LettuceConnectionFactory(redisNode.getHost(), redisNode.getPort()));
                Set<String> keys = new HashSet<>();
                ScanOptions scanOptions = ScanOptions.scanOptions().match("order::cacheLog::").count(1000).build();
                Set<String> allKeys = curTemplate.execute(new RedisCallback<Set<String>>() {
                    @Override
                    public Set<String> doInRedis(RedisConnection connection) throws DataAccessException {
                        Set<String> temp = new HashSet<>();
                        Cursor<byte[]> scanCursor = connection.scan(scanOptions);
                        while (scanCursor.hasNext()) {
                            temp.add(new String(scanCursor.next()));
                        }
                        return temp;
                    }
                });
                // 如果缓存中还能扫描到key
                for (String curKey : allKeys) {
                    // 直连节点，不需要做{}路由分片了
                    BoundHashOperations<String, String, Integer> logMapOps = redisOrderLogTemplate.boundHashOps(curKey);
                    // 情况1
                    if (!logMapOps.hasKey("state")) {
                        redisOrderLogTemplate.delete(curKey);
                    }
                    // 情况3
                    else if (logMapOps.get("state").equals(CacheOrderStatusEnum.CREATING.getCode())) {
                        Set<String> products = logMapOps.keys();
                        OrderSubmitVo orderSubmitVo = new OrderSubmitVo();
                        orderSubmitVo.setOrderSN(curKey.split("::")[3]);
                        for (String skuId : products) {
                            List<OrderItemNewTo> orderItemNewTos = new ArrayList<>();
                            if (!skuId.equals("state")) {
                                OrderItemNewTo orderItemNewTo = new OrderItemNewTo();
                                orderItemNewTo.setSkuId(Long.parseLong(skuId));
                                orderItemNewTo.setCount(logMapOps.get(skuId));
                                orderItemNewTos.add(orderItemNewTo);
                            }
                            orderSubmitVo.setOrderItemNewToList(orderItemNewTos);
                        }
                        buildOrderItemToList(orderSubmitVo);
                        /**
                         * 补偿线程中此处可能导致重复消费
                         */
                        rabbitTemplate.convertAndSend("order-event-exchange", "order.create.unpay", orderSubmitVo);
                        redisOrderLogTemplate.delete(curKey);
                    }
                    // 情况2
                    else {
                        Set<String> products = logMapOps.keys();
                        OrderSubmitVo orderSubmitVo = new OrderSubmitVo();
                        orderSubmitVo.setOrderSN(curKey.split("::")[3]);
                        for (String skuId : products) {
                            List<OrderItemNewTo> orderItemNewTos = new ArrayList<>();
                            if (!skuId.equals("state")) {
                                OrderItemNewTo orderItemNewTo = new OrderItemNewTo();
                                orderItemNewTo.setSkuId(Long.parseLong(skuId));
                                Integer temp = logMapOps.get(skuId);
                                orderItemNewTo.setCount(Math.abs(temp));
                                if (temp < 0) {
                                    /**
                                     * 重复submitOrderForAllType()中的预扣除逻辑
                                     */
                                }
                                orderItemNewTos.add(orderItemNewTo);
                            }
                        }
                    }
                }
            }, workCrashThreadPoolExecutor));
        }
    }


    /**
     * 解析并保存新创建的订单.
     * 只要保证自身是本地事务即可，不再需要使用分布式事务
     *
     * @param orderCreateVo
     */
    @Override
    @Transactional
    public void saveCreateOrder(OrderCreateVo orderCreateVo) {
        OrderEntity newCreateOrder = orderCreateVo.getOrderEntity();
        newCreateOrder.setCreateTime(new Date());
        newCreateOrder.setUpdateTime(new Date());
        // 保存总订单
        newCreateOrder.setDelete(DatabaseConstant.UN_LOGISTIC_DELETED);
        orderDao.insert(newCreateOrder);
        List<OrderItemEntity> orderItemEntityList = orderCreateVo.getOrderItemEntityList();
        // 修改商品小项中所属总订单orderId的信息
        orderItemEntityList.forEach(orderItemEntity -> {
            orderItemEntity.setOrderId(newCreateOrder.getId());
        });
        orderItemDao.insertBatch(orderItemEntityList);
    }

    @Override
    @Transactional
    public void saveCreateDoneOrder(OrderCreateVo orderCreateVo) {

    }


    /**
     * 关闭一个订单。可能是超时订单，也可能是主动关闭的订单
     */
    @Override
    @ClearCache(prefix = "order:list:user:#orderTo.memberId")
    public void closeOrder(OrderTo orderTo) {
        Long changeRes = orderDao.changeOrderStatus(orderTo.getOrderSn(), OrderStatusEnum.CREATING.getCode(), OrderStatusEnum.CANCELED.getCode());
        if (changeRes != null && changeRes.equals(1L)) {
            logger.info("关闭" + orderTo.getOrderSn() + "号待支付订单");
            // 立刻主动地给库存服务的死信队列发消息，要求立刻解锁库存
            rabbitTemplate.convertAndSend("order-event-exchange", "order.release.other.#", orderTo);
        } else {
            throw new RRException(BizCodeEnum.ORDER_CLOSE_ERROR.getMessage(), BizCodeEnum.ORDER_CLOSE_ERROR.getCode());
        }
    }

    /**
     * 根据orderSN制作支付vo
     *
     * @param orderSn
     * @return
     */
    @Override
    public PayTo getOrderPayVo(Long memberId, String orderSn) {
        PayTo payTo = new PayTo();
        // 查询未支付订单详情。涉及核心业务，强制数据库查询
        OrderEntity order = getUnpayOrder(memberId, orderSn);
        if (order == null) {
            return null;
        } else {
            BigDecimal payAmount = order.getPayAmount().setScale(2, BigDecimal.ROUND_UP);
            payTo.setTotal_amount(payAmount.toString());
            payTo.setOut_trade_no(orderSn);
            payTo.setSubject("商城支付");
            payTo.setBody("");
            return payTo;
        }
    }

    /**
     * 带缓查询当前用户的所有订单(包括总订单和拆分后的小订单)。
     * 由于用户的个人订单量是很少的，和总订单量没法比，所以不用做分页，全进缓存即可
     *
     * @param memberId 用户Id
     * @return
     */
    @Override
    @WithCache(prefix = "order:list:user:#memberId", isList = true, returnType = OrderListVo.class)
    public List<OrderListVo> getOrderListAll(Long memberId) {
        /**
         * 并发量少时可以用JOIN一次查出订单-子订单-子订单商品
         */
        // orderDao.selectOrderSubOrderItemOnce(memberId);

        List<OrderEntity> orderEntityList = orderDao.selectAllOrderByMemberId(memberId);

        // 制作 OrderListVoList 并返回
        return orderEntityList.stream().map(orderEntity -> {
            // 此时每一个orderEntity就是一个未经分割的总订单，要将其对应的切分子订单查出来
            List<SubOrderEntity> subOrderEntityList = subOrderDao.selectAllRelationByOrderSn(orderEntity.getOrderSn());
            /**
             * 不存在子订单，说明当前总订单并没有切分，即订单仍处于待支付状态
             */
            if (subOrderEntityList == null || subOrderEntityList.size() == 0) {
                // 那么直接不再去查子订单，此时subOrderVoList字段为空
                OrderListVo orderListVo = new OrderListVo();
                BeanUtils.copyProperties(orderEntity, orderListVo);
                // 然后把当前总订单所有子订单的购物小项都保存在总订单Vo中
                List<OrderItemEntity> currentTopOrderItemList = orderItemDao.selectItemByOrderSn(orderEntity.getOrderSn());
                orderListVo.setOrderItemEntityList(currentTopOrderItemList);
                orderListVo.setSubOrderVoList(null);
                return orderListVo;
            }
            /**
             * 存在子订单
             */
            else {
                List<OrderItemEntity> currentTopOrderItemList = new ArrayList<>();  // 保存当前总订单的全部商品
                List<SubOrderVo> subOrderVoList = subOrderEntityList.stream().map(subOrder -> {
                    SubOrderVo subOrderVo = new SubOrderVo();
                    BeanUtils.copyProperties(subOrder, subOrderVo);
                    List<OrderItemEntity> subOrderItems = orderItemDao.selectItemByOrderSn(subOrder.getOrderSn());
                    subOrderVo.setOrderItemEntityList(subOrderItems);
                    currentTopOrderItemList.addAll(subOrderItems);
                    return subOrderVo;
                }).collect(Collectors.toList());
                OrderListVo orderListVo = new OrderListVo();
                BeanUtils.copyProperties(orderEntity, orderListVo);
                orderListVo.setSubOrderVoList(subOrderVoList);
                orderListVo.setOrderItemEntityList(currentTopOrderItemList);
                return orderListVo;
            }
        }).collect(Collectors.toList());
    }

    /**
     * 带缓查询当前用户的分页总订单。
     * 这里的带缓逻辑有点复杂，不能简单用注解实现。
     *
     * @param memberId 用户Id
     * @return
     */
    @Override
    public List<OrderListVo> getOrderListPage(Long memberId, Integer page, Integer pageSize) {
        int pageStart = (page - 1) * pageSize;
        int pageEnd = page * pageSize;

        List<OrderListVo> orderListAll = new ArrayList<>();
        if (!stringRedisTemplate.hasKey("order:list:user:" + memberId)) {
            orderListAll = getOrderListAll(memberId);
        } else {
            orderListAll = JSON.parseArray(stringRedisTemplate.opsForValue().get("order:list:user:" + memberId), OrderListVo.class);
        }

        List<OrderListVo> orderListPage = new ArrayList<>();
        for (int index = pageStart; index < Math.min(pageEnd, orderListAll.size()); index++) {
            orderListPage.add(orderListAll.get(index));
        }

        return orderListPage;
    }

    /**
     * 查询当前用户指定订单
     *
     * @param memberId
     * @param orderSn
     */
    @Override
    public OrderListVo getOrderBySN(Long memberId, String orderSn) {
        return getOrderListAll(memberId).stream().filter(orderListVo -> {
            return orderListVo.getOrderSn().equals(orderSn);
        }).collect(Collectors.toList()).get(0);
    }

    /**
     * 查询指定未支付订单的详情。设计核心业务，强制从数据库查询
     *
     * @param memberId
     * @param orderSN  订单SN
     */
    @Override
    public OrderEntity getUnpayOrder(Long memberId, String orderSN) {
        return orderDao.selectByMemberIdAndOrderSnAndStatus(memberId, orderSN, OrderStatusEnum.CREATING);
    }

    /**
     * 带缓查询指定任意订单的详情。优先从缓存中的用户订单列表中查询
     *
     * @param memberId
     * @param orderSN  订单SN
     */
    @Override
    public OrderListVo getAnyOrder(Long memberId, String orderSN) {
        List<OrderListVo> orderListALL = getOrderListAll(memberId);
        List<OrderListVo> selected = orderListALL.stream().filter(orderListVo -> {
            return orderListVo.getOrderSn().equals(orderSN);
        }).collect(Collectors.toList());
        return selected.get(0);
    }

    /**
     * 结合提交订单页面传递来的有限的信息，再去数据库购物车把打钩项商品查一遍，再构建一个订单，这是最后一次和后端同步了.
     * 同时最终的商品价格会在此处被最终确定.
     * <p>
     * 普通订单: 再去数据库根据购物车构建最终下单订单。
     * 秒杀订单: 由于秒杀商品不入购物车，所以直接根据秒杀信息确定商品即可。
     * <p>
     * 此方法仅查询数据库，无修改.
     *
     * @return OrderCreateVo
     */
    public OrderCreateVo createOrderBySubmit(OrderSubmitVo orderSubmitVo) {
        LoginSuccessTo loginSuccessTo = JSON.parseObject((String) ThreadLocalContextHolder.get(AuthConstant.LOGIN_USER_THREADLOCAL_NAME), LoginSuccessTo.class);
        OrderCreateVo orderCreateVo = new OrderCreateVo();

        /**
         * 1. 创建 未经切分的总订单 并填充基础信息(NS订单同逻辑)
         */
        OrderEntity orderEntity = new OrderEntity();
        // 直接用IdWorker可以快速生成各种Sn
        String orderSn = SnWorker.getTimeId();
        orderEntity.setOrderSn(orderSn);
        // 订单状态、收货状态、删除状态
        orderEntity.setStatus(OrderStatusEnum.CREATING.getCode());
        orderEntity.setConfirmStatus(OrderStatusEnum.UNCONFIRMRECEIVED.getCode());
        orderEntity.setDelete(OrderStatusEnum.UNDELETED.getCode());
        orderEntity.setMemberId(loginSuccessTo.getId());
        orderEntity.setMemberUsername(loginSuccessTo.getUsername());

        /**
         * 2. 填充收货地址和运费(NS订单同逻辑)
         */
        buildAddressFare(orderSubmitVo, orderEntity);

        /**
         * 3. 填充所有订单小项信息(NS订单会有不同的逻辑)
         */
        List<OrderItemEntity> orderItemEntityList = buildOrderItemEntityList(orderSn, orderSubmitVo);
        orderCreateVo.setOrderItemEntityList(orderItemEntityList);

        if (orderItemEntityList == null || orderItemEntityList.size() == 0) {
            orderEntity.setPayAmount(new BigDecimal(0));
            orderCreateVo.setOrderEntity(orderEntity);
            return orderCreateVo;
        } else {
            // 4. 计算订单价格
            orderCreateVo = computePrice(orderCreateVo, orderEntity, orderItemEntityList, orderSubmitVo);
            return orderCreateVo;
        }
    }

    @Override
    public void confirmOrder(Long memberId, String orderSN) {

    }

    /**
     * 填充订单小项信息.
     * 此时订单处于未经切分的状态，所以所有订单小项的orderSn都先写总订单sn.
     * 普通订单: 最后一次去数据库查询购物车选中商品，并去数据库获取商品最新价格.
     * 秒杀订单: 直接根据缓存的秒杀信息构建
     *
     * @return
     */
    public List<OrderItemEntity> buildOrderItemEntityList(String orderSn, OrderSubmitVo orderSubmitVo) {
        // 获取在下单消息投递前的校验阶段固定的订单小项列表
        List<OrderItemTo> orderItemChecked = orderSubmitVo.getOrderItemToList();

        /**
         * 开始填充订单小项
         */
        if (orderItemChecked != null && orderItemChecked.size() > 0) {
            return orderItemChecked.stream().map(orderItemTo -> {
                OrderItemEntity orderItemEntity = new OrderItemEntity();
                // 订单小项所属的未经切分的总订单号
                orderItemEntity.setOrderSn(orderSn);

                // RPC - 查询SpuInfo
                R spuBySkuIdR = productDubboInterface.getSpuBySkuId(orderItemTo.getSkuId());
                if (spuBySkuIdR.getCode() != 0) {
                    logger.error(spuBySkuIdR.getMsg());
                }
                SpuInfoTo spuInfoTo = JSON.parseObject((String) spuBySkuIdR.get("spuInfoTo"), SpuInfoTo.class);
                orderItemEntity.setSpuId(spuInfoTo.getId());
                orderItemEntity.setSpuName(spuInfoTo.getSpuName());
                orderItemEntity.setSpuBrand(spuInfoTo.getBrandId().toString());
                orderItemEntity.setCategoryId(spuInfoTo.getCatalogId());

                // RPC - 查询sku商品积分信息。
                R boundsBySpuIdR = couponDubboInterface.getBoundsBySpuId(spuInfoTo.getId());
                if (boundsBySpuIdR.getCode() != 0) {
                    logger.error(boundsBySpuIdR.getMsg());
                }
                BigDecimal[] boundsBySpuId = (BigDecimal[]) boundsBySpuIdR.get("bounds");
                // 计算商品积分，由spu积分信息 + 买了多少商品决定的。
                if (boundsBySpuId != null) {
                    orderItemEntity.setGiftGrowth(boundsBySpuId[0].multiply(new BigDecimal(orderItemTo.getCount().toString())).intValue());
                    orderItemEntity.setGiftIntegration(boundsBySpuId[1].multiply(new BigDecimal(orderItemTo.getCount().toString())).intValue());
                } else {
                    orderItemEntity.setGiftGrowth(0);
                    orderItemEntity.setGiftIntegration(0);
                }

                // 填充sku信息
                orderItemEntity.setSkuId(orderItemTo.getSkuId());
                orderItemEntity.setSkuName(orderItemTo.getTitle());
                orderItemEntity.setSkuPic(orderItemTo.getImage());
                orderItemEntity.setSkuPrice(orderItemTo.getPrice());
                orderItemEntity.setSkuQuantity(orderItemTo.getCount());
                orderItemEntity.setSkuAttrsVals(StringUtils.collectionToDelimitedString(orderItemTo.getSkuAttr(), ";"));

                orderItemEntity.setRealAmount(orderItemEntity.getSkuPrice().multiply(new BigDecimal(orderItemEntity.getSkuQuantity().toString())));
                return orderItemEntity;
            }).collect(Collectors.toList());
        }
        return null;
    }

    /**
     * 锁定订单中商品的价格，之后的消费中不能再改变价格
     */
    public void buildOrderItemToList(OrderSubmitVo orderSubmitVo) {
        for (OrderItemNewTo orderItemNewTo : orderSubmitVo.getOrderItemNewToList()) {
            // 促销商品
            if (orderItemNewTo.getSeckillSessionId() != null && orderItemNewTo.getSeckillSessionId() != 0L) {
                String price = (String) redisProductTemplate.opsForHash().get("seckill::product::{" + orderItemNewTo.getSkuId() + "}", "price");
                orderItemNewTo.setPrice(new BigDecimal(price));
            }
            // 普通商品
            else {
                String price = (String) redisProductTemplate.opsForHash().get("product::{" + orderItemNewTo.getSkuId() + "}", "price");
                orderItemNewTo.setPrice(new BigDecimal(price));
            }
        }
    }

    /**
     * 根据订单检查页前端提交，填充地址和运费.
     *
     * @param orderSubmitVo 订单检查页前端提交
     * @param orderEntity   填充前的 OrderEntity
     */
    public void buildAddressFare(OrderSubmitVo orderSubmitVo, OrderEntity orderEntity) {
        /**
         * 填充收货地址
         */
        MemberReceiveAddressTo memberReceiveAddressTo = orderSubmitVo.getMemberReceiveAddressTo();
        orderEntity.setReceiverProvince(memberReceiveAddressTo.getProvince());
        orderEntity.setReceiverCity(memberReceiveAddressTo.getCity());
        orderEntity.setReceiverRegion(memberReceiveAddressTo.getRegion());
        orderEntity.setReceiverDetailAddress(memberReceiveAddressTo.getDetailAddress());
        orderEntity.setReceiverName(memberReceiveAddressTo.getName());
        orderEntity.setReceiverPhone(memberReceiveAddressTo.getPhone());
        orderEntity.setReceiverPostCode(memberReceiveAddressTo.getPostCode());

        /**
         * RPC 根据收货地址计算运费
         */
        R getFareR = memberDubboInterface.getFare(orderSubmitVo.getMemberReceiveAddressTo().getId());
        if (getFareR.getCode() != 0) {
            logger.error(getFareR.getMsg());
            throw new RRException(getFareR.getMsg(), BizCodeEnum.RPC_ERROR.getCode());
        }
        BigDecimal fare = (BigDecimal) getFareR.get("fare");
        orderEntity.setFreightAmount(fare);
    }


    /**
     * 计算未经拆分的总订单价格
     * 同时还要计算 每个订单小项 运用上整体优惠之后的实际价格
     *
     * @param orderEntity
     * @param orderItemEntityList
     */
    public OrderCreateVo computePrice(OrderCreateVo orderCreateVo, OrderEntity orderEntity, List<OrderItemEntity> orderItemEntityList, OrderSubmitVo orderSubmitVo) {
        if (orderItemEntityList == null || orderItemEntityList.size() == 0) {
            orderCreateVo.setOrderEntity(orderEntity);
            orderCreateVo.setOrderItemEntityList(orderItemEntityList);
            return orderCreateVo;
        }
        BigDecimal orderTotal = new BigDecimal("0");
        Integer orderGrowth = 0;
        Integer orderIntegration = 0;
        for (OrderItemEntity orderItemEntity : orderItemEntityList) {
            BigDecimal skuRealPrice = orderItemEntity.getRealAmount();
            orderTotal = orderTotal.add(skuRealPrice);
            orderGrowth = orderItemEntity.getGiftGrowth() + orderGrowth;
            orderIntegration = orderItemEntity.getGiftIntegration() + orderIntegration;
        }
        // 订单总额就是不算各种优惠和运费的总额
        orderEntity.setTotalAmount(orderTotal);
        // 订单使用的积分/折扣金额，这个就是根据前端传来的
        orderEntity.setUseIntegration(orderSubmitVo.getIntegration());
        orderEntity.setIntegrationAmount(orderSubmitVo.getIntegrationPrice());
        // 订单应付总金额=原价-折扣+运费
        orderEntity.setPayAmount(orderEntity.getTotalAmount().add(orderEntity.getFreightAmount()).subtract(orderEntity.getIntegrationAmount()));
        // 各个订单小项实际金额 = 原价-折扣加权平均, 各个订单小项实际消耗积分=总消耗加权平均
        if (orderTotal.compareTo(new BigDecimal("0")) != 0) {
            BigDecimal finalOrderTotal = orderTotal;
            orderItemEntityList.forEach(orderItemEntity -> {
                // 分解后实际消耗积分金额
                BigDecimal realIntegrationAfterYouHui = (orderSubmitVo.getIntegrationPrice()
                        .multiply(orderItemEntity.getRealAmount()
                                .divide(finalOrderTotal, BigDecimal.ROUND_HALF_UP)))
                        .setScale(2, BigDecimal.ROUND_HALF_UP);

                // 分解后实际金额
                BigDecimal realAmountAfterYouHui = orderItemEntity.getRealAmount()
                        .subtract(realIntegrationAfterYouHui);

                orderItemEntity.setRealAmount(realAmountAfterYouHui);
                orderItemEntity.setIntegrationAmount(realIntegrationAfterYouHui);
            });
        }
        // 订单的总积分，将所有sku加起来
        orderEntity.setGrowth(orderGrowth);
        orderEntity.setIntegration(orderIntegration);
        orderCreateVo.setOrderEntity(orderEntity);
        orderCreateVo.setOrderItemEntityList(orderItemEntityList);
        return orderCreateVo;
    }


    /**
     * 保存未经拆分的总订单数据。
     *
     * @param orderCreateVo
     */
    @Transactional
    @Override
    @ClearCache(prefix = "order:list:user:#orderCreateVo.orderEntity.memberId")
    public void saveUnpayOrder(BusinessActionContext businessActionContext, OrderCreateVo orderCreateVo) {
        OrderEntity newCreateOrder = orderCreateVo.getOrderEntity();
        newCreateOrder.setCreateTime(new Date());
        newCreateOrder.setUpdateTime(new Date());
        // 使用事务，保证当ES发现总订单可见时，订单小项一定可见(因为代表着当前事务全部成功)
        OrderEntity isExisted = orderDao.selectByMemberIdAndOrderSnAndStatus(null, orderCreateVo.getOrderEntity().getOrderSn(), null);
        if (isExisted != null) {
            // 保存大订单，自动填充主键。MySQL事务保证之后插入的数据不管是不是用事务，都不会占用这里自动填充的主键
            newCreateOrder.setDelete(DatabaseConstant.UN_LOGISTIC_DELETED);
            orderDao.insert(newCreateOrder);
            List<OrderItemEntity> orderItemEntityList = orderCreateVo.getOrderItemEntityList();
            // 修改商品小项中所属总订单orderId的信息
            orderItemEntityList.forEach(orderItemEntity -> {
                orderItemEntity.setOrderId(newCreateOrder.getId());
            });
            orderItemDao.insertBatch(orderItemEntityList);
        }
    }

    /**
     * 保存未经拆分的总订单数据 提交动作。直接返回true
     *
     * @param businessActionContext
     * @return
     */
    @Override
    @Transactional
    public boolean saveUnpayOrderCommit(BusinessActionContext businessActionContext) {
        return true;
    }

    /**
     * 保存未经拆分的总订单数据 回滚动作。逻辑删除一阶段插入的父订单，订单小项无需修改。
     * 同样有两种: 1. 自身一阶段回滚，被SB自身清除。 2. 之后的分布式事务分支回滚导致的全局回滚.
     * 由于是删除动作，所以很好办，直接删，要是情况1啥都没有无所谓删，情况2那正好就删了。
     *
     * @param businessActionContext
     * @return
     */
    @Override
    @Transactional
    public boolean saveUnpayOrderRollback(BusinessActionContext businessActionContext) {
        OrderCreateVo orderCreateVo = (OrderCreateVo) businessActionContext.getActionContext("orderCreateVo");
        try {
            OrderEntity orderEntity = orderCreateVo.getOrderEntity();
            orderDao.logicDeleteByOrderSn(orderEntity.getOrderSn());
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 收到支付成功回调后，如果是成功支付，则拆分总订单
     *
     * @param businessActionContext
     * @param payAsyncTo
     */
    @Override
    @Transactional
    @ClearCache(prefix = "order:list:user:#payAsyncTo.buyer_id")
    public void savePayedSubOrder(BusinessActionContext businessActionContext, PayAsyncTo payAsyncTo) {
        // 获得未经拆分的总订单SN 以及对应的总订单本身
        String orderSn = payAsyncTo.getOut_trade_no();
        OrderEntity topOrderEntity = orderDao.selectByMemberIdAndOrderSnAndStatus(null, orderSn, null);

        /**
         * 开辟空间
         */
        // 根据总订单去查 订单小项。然后根据 spu_brand 来进行订单切分
        List<OrderItemEntity> orderItemEntityList = orderItemDao.selectItemByOrderSn(orderSn);
        // 开辟列表。用于保存切分后的子订单
        List<SubOrderEntity> subOrderEntityList = new ArrayList<>();
        // 开辟列表。用于保存总订单-子订单的关系
        List<OrderSubOrderRelationEntity> orderSubOrderRelationEntityList = new ArrayList<>();

        /**
         * 中间HashMap，按照 品牌 - 订单小项 切分父订单中的所有商品
         */
        HashMap<String, List<OrderItemEntity>> brand_item_map = new HashMap<>();
        for (OrderItemEntity orderItemEntity : orderItemEntityList) {
            if (!brand_item_map.containsKey(orderItemEntity.getSpuBrand())) {
                List<OrderItemEntity> tempList = new ArrayList<>();
                tempList.add(orderItemEntity);
                brand_item_map.put(orderItemEntity.getSpuBrand(), tempList);
            } else {
                brand_item_map.get(orderItemEntity.getSpuBrand()).add(orderItemEntity);
            }
        }

        // 当前总订单总共被拆分出多少个品牌，每个品牌有一系列子订单。
        Set<String> allBrand = brand_item_map.keySet();

        if (allBrand.size() == 1) {
            /**
             * 若当前总订单只含有一个品牌，那就不用再额外切分了，则直接复制总订单为新的子订单，也不更改子订单的ordersn，因为两张订单的属性完全相同.
             * 同时也不会修改订单小项绑定的订单SN，仍然保持下单时绑定的父订单SN即可.
             */
            SubOrderEntity subOrderEntity = new SubOrderEntity();
            BeanUtils.copyProperties(topOrderEntity, subOrderEntity);
            subOrderEntity.setId(null);
            subOrderEntity.setFreightAmount(new BigDecimal("0"));
            subOrderDao.insert(subOrderEntity);
            // 保存关系单
            OrderSubOrderRelationEntity orderSubOrderRelationEntity = new OrderSubOrderRelationEntity();
            orderSubOrderRelationEntity.setOrderSn(orderSn);
            orderSubOrderRelationEntity.setSubOrderSn(subOrderEntity.getOrderSn());
            orderSubOrderRelationEntity.setDelete(DatabaseConstant.UN_LOGISTIC_DELETED);
            orderSubOrderRelationDao.insert(orderSubOrderRelationEntity);
        } else {
            /**
             * 若当前总订单需要被拆分，则为拆分出的每一个子订单赋予新的orderSn，并计算各个子订单的新价格.
             * 同时修改子订单对应的商品小项绑定的订单SN，绑定到子订单SN.
             */
            List<SubOrderEntity> subOrderEntities = new ArrayList<>();
            List<OrderSubOrderRelationEntity> orderSubOrderRelationEntities = new ArrayList<>();
            for (String brandId : allBrand) {
                List<OrderItemEntity> currentBrandItem = brand_item_map.get(brandId);
                String currentSubOrderSn = SnWorker.getTimeId();
                BigDecimal currentSubOrderAmount = new BigDecimal("0");
                BigDecimal currentSubOrderIntegrationAmount = new BigDecimal("0");
                int currentSubOrderIntegration = 0;
                int currentSubOrderGrowth = 0;

                // 计算当前子订单 应用平均优惠后的实际总价格
                // 计算当前子订单 使用的分解后积分
                // 计算当前子订单 总积分
                // 计算当前子订单 总成长值
                // 将商品小项的所属订单更改为当前子订单的订单sn
                for (OrderItemEntity orderItemEntity : currentBrandItem) {
                    currentSubOrderAmount = currentSubOrderAmount.add(orderItemEntity.getRealAmount());
                    currentSubOrderIntegrationAmount = currentSubOrderIntegrationAmount.add(orderItemEntity.getIntegrationAmount());
                    currentSubOrderIntegration = currentSubOrderIntegration + orderItemEntity.getGiftIntegration();
                    currentSubOrderGrowth = currentSubOrderGrowth + orderItemEntity.getGiftGrowth();
                    orderItemEntity.setOrderSn(currentSubOrderSn);
                    orderItemDao.updateItemBelongOrderSn(orderItemEntity);  // 更新绑定为子订单的订单小项
                }

                SubOrderEntity subOrderEntity = new SubOrderEntity();
                // 子订单先直接复制总订单的属性
                BeanUtils.copyProperties(topOrderEntity, subOrderEntity);
                // 更改子订单的sn为新产生的sn
                subOrderEntity.setOrderSn(currentSubOrderSn);
                // 将子订单的id删除
                subOrderEntity.setId(null);
                // 设置子订单的实际支付
                subOrderEntity.setPayAmount(currentSubOrderAmount);
                // 子订单的总价设置为0
                subOrderEntity.setTotalAmount(new BigDecimal("0"));
                // 子订单运费设置为0
                subOrderEntity.setFreightAmount(new BigDecimal("0"));
                // 子订单优惠金额
                subOrderEntity.setIntegrationAmount(currentSubOrderIntegrationAmount);
                // 子订单状态此时和总订单保持相同为 1-已付款，0-待收货，0-未删除
                // 子订单的积分和成长值
                subOrderEntity.setIntegration(currentSubOrderIntegration);
                subOrderEntity.setGrowth(currentSubOrderGrowth);
                // 子订单使用的积分赞设为0
                subOrderEntity.setUseIntegration(0);

                subOrderEntities.add(subOrderEntity);

                // 保存关系单
                OrderSubOrderRelationEntity orderSubOrderRelationEntity = new OrderSubOrderRelationEntity();
                orderSubOrderRelationEntity.setOrderSn(orderSn);
                orderSubOrderRelationEntity.setSubOrderSn(subOrderEntity.getOrderSn());
                orderSubOrderRelationEntities.add(orderSubOrderRelationEntity);
            }
            subOrderDao.insertBatch(subOrderEntities);
            orderSubOrderRelationDao.insertBatch(orderSubOrderRelationEntities);
        }
    }

    /**
     * 收到支付成功回调后，如果是成功支付，则拆分总订单 提交动作。
     * 实际上不做任何操作
     *
     * @param businessActionContext
     * @return
     */
    @Override
    @Transactional
    public boolean savePayedSubOrderCommit(BusinessActionContext businessActionContext) {
        return true;
    }

    /**
     * 收到支付成功回调后，如果是成功支付，则拆分总订单 回滚动作。
     * 将一阶段插入的所有父子订单逻辑删除，父子订单关系表保持不变，订单小项也保持不变.
     *
     * @param businessActionContext
     * @return
     */
    @Override
    @Transactional
    public boolean savePayedSubOrderRollback(BusinessActionContext businessActionContext) {
        PayAsyncTo payAsyncTo = (PayAsyncTo) businessActionContext.getActionContext("payAsyncTo");
        try {
            // 获得未经拆分的父订单SN
            String orderSn = payAsyncTo.getOut_trade_no();
            // 查询总订单SN对应的子订单SN列表
            List<String> subOrderSnList = orderSubOrderRelationDao.selectSubOrderSnByOrderSn(orderSn);
            // 将所有子订单逻辑删除
            if (subOrderSnList != null && !subOrderSnList.isEmpty()) {
                for (String subOrderSn : subOrderSnList) {
                    subOrderDao.logicDeleteByOrderSn(subOrderSn);
                }
            }
            // 将父订单逻辑删除
            orderDao.logicDeleteByOrderSn(orderSn);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * <b>已弃用</b>
     * 秒杀订单提交订单。全面使用消息队列进行服务解耦
     *
     * @param orderSubmitVo
     * @return 等待支付页。但是注意，秒杀高并发下，只能保证缓存中一定存在订单，数据库中订单是否入库未知
     */
    @Override
    public R submitSeckillOrder(OrderSubmitVo orderSubmitVo) {
        // 在业务代码前验证信号量，如果售罄则不进行接下来的内容
        RSemaphore semaphore = redissonClient.getSemaphore(SecKillConstant.SEC_PRODUCT_SEMAPHORE + orderSubmitVo.getSeckillRandomCode());
        if (semaphore == null || semaphore.availablePermits() == 0) {
            return R.error("秒杀商品已售罄");
        }

        // 先从ThreadLocal中把用户信息拿出来
        LoginSuccessTo loginSuccessTo = JSON.parseObject((String) ThreadLocalContextHolder.get(AuthConstant.LOGIN_USER_THREADLOCAL_NAME), LoginSuccessTo.class);
        // 把主线程中的上下文信息提取出来，用于在异步任务时强制同步
        RequestAttributes mainThreadContext = RequestContextHolder.getRequestAttributes();
        // 这里对秒杀信息做最后一次验证，依然是为了防止有人直接从URL发请求过来。这里是最后一次验证了

        /**
         * 从缓存预热中查出这个秒杀活动。
         * 由于所有秒杀必须在缓存中预热，所以这里没有击穿风险
         */
        String currentSecKillSessionJSON = stringRedisTemplate.opsForValue().get(SecKillConstant.SEC_SESSION_PREFIX + orderSubmitVo.getSeckillSessionId() + ":*");

        if (StringUtils.isEmpty(currentSecKillSessionJSON)) {
            // redis中已经没有这个秒杀商品了
            return R.error("该秒杀活动已结束");
        } else {
            // 当前的秒杀活动
            SecKillSessionTo currentSecKillSessionTo = JSON.parseObject(currentSecKillSessionJSON, SecKillSessionTo.class);
            // 寻找到当前秒杀活动含有的该秒杀商品的详情。
            SecKillProductCacheTo cacheTo = currentSecKillSessionTo.getSecKillProductCacheToList().stream().filter(secKillProductCacheTo -> {
                return secKillProductCacheTo.getSecKillProductTo().getSkuId().equals(orderSubmitVo.getSeckillSkuId());
            }).collect(Collectors.toList()).get(0);

            if (!cacheTo.getSecKillProductTo().getSkuId().equals(orderSubmitVo.getSeckillSkuId())) {
                // 合法性校验失败。skuId对不上
                return R.error("合法性校验失败:该商品不在当前秒杀活动中，请严格从秒杀商品详情页进入秒杀活动");
            } else {
                Long startTime = cacheTo.getStartTime();
                Long endTime = cacheTo.getEndTime();
                long curtime = new Date().getTime();
                if (curtime < startTime || curtime > endTime) {
                    // 合法性校验失败。时间不对
                    return R.error("合法性校验失败:该秒杀活动尚未开始，请严格从秒杀商品详情页进入秒杀活动");
                } else if (!cacheTo.getRandomCode().equals(orderSubmitVo.getSeckillRandomCode())) {
                    // 合法性校验失败，随机码不对
                    return R.error("合法性校验失败:您提交了错误的商品秘钥，请严格从秒杀商品详情页进入秒杀活动");
                } else if (cacheTo.getSecKillProductTo().getSeckillUserLimit() <= orderSubmitVo.getSeckillNum()) {
                    // 合法性校验失败，单次购买超过限制
                    return R.error("合法性校验失败:单次购买数量超出限额");
                } else {
                    // 验证当前用户累计购买是否超过限制，同样使用信号量锁实现
                    String redisKey = SecKillConstant.SEC_USER_PRODUCT_COUNT + orderSubmitVo.getSeckillSessionId().toString() + "_" + loginSuccessTo.getId().toString();
                    String userCount = stringRedisTemplate.opsForValue().get(redisKey);
                    if (!StringUtils.isEmpty(userCount) &&
                            Integer.parseInt(userCount) + orderSubmitVo.getSeckillNum() > cacheTo.getSecKillProductTo().getSeckillUserLimit()) {
                        return R.error("合法性校验失败:您购买此商品的数量已达到限额，请减少购买数量");
                    } else {
                        // 记录下用户最新的累计购买数量。注意此时还不能直接修改redis，因为现在也不能确定用户购买成功
                        userCount = !StringUtils.isEmpty(userCount) ?
                                String.valueOf((Integer.parseInt(userCount) + orderSubmitVo.getSeckillNum())) :
                                orderSubmitVo.getSeckillNum().toString();
                        // 尝试扣减信号量锁，注意是扣减，这里是真的在秒杀了
                        try {
                            boolean tryAcquire = semaphore.tryAcquire(orderSubmitVo.getSeckillNum(), 1, TimeUnit.SECONDS);
                            if (!tryAcquire) {
                                return R.error("该商品已售罄");
                            } else {
                                // 此时秒杀成功。
                                // 设置用户累计购买数量
                                stringRedisTemplate.opsForValue().set(redisKey, userCount);
                                // 处理秒杀成功事件。包括构造秒杀总订单入缓存，消息队列发送消息.
                                // 这里是阻塞等待，因为必须明确是否成功
                                try {
                                    orderSubmitVo.setSecKillProductCacheTo(cacheTo);
                                    OrderCreateVo orderCreateVo = handleSuccessSecKill(orderSubmitVo);
                                    return R.ok().put("order", orderCreateVo);
                                } catch (Exception e) {
                                    // 此时如果异常则应该回滚信号量
                                    semaphore.addPermits(orderSubmitVo.getSeckillNum());
                                    e.printStackTrace();
                                    return R.error("系统未知异常，秒杀失败");
                                }
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            return R.error("库存未知异常，秒杀失败");
                        }
                    }
                }
            }
        }
    }

    /**
     * <b>已弃用</b>
     * 处理秒杀成功事件。包括构造秒杀总订单，入缓存，消息队列发送消息等。
     * 直接对接原有的订单生成系统。只是不会进行购物车检查。
     */
    public OrderCreateVo handleSuccessSecKill(OrderSubmitVo orderSubmitVo) {
        LoginSuccessTo loginSuccessTo = JSON.parseObject((String) ThreadLocalContextHolder.get(AuthConstant.LOGIN_USER_THREADLOCAL_NAME), LoginSuccessTo.class);
        // 构建订单
        OrderCreateVo orderCreateVo = createOrderBySubmit(orderSubmitVo);
        // 订单入缓存, 使用 seckill:order:未拆分总订单的orderSN:userId 作为key
        stringRedisTemplate.opsForValue().set("seckill:order:" + orderCreateVo.getOrderEntity().getOrderSn() + ":" + loginSuccessTo.getId(),
                JSON.toJSONString(orderCreateVo));
        // 该队列用于将未支付状态的订单入库
        rabbitTemplate.convertAndSend("order-event-exchange", "order.seckill.new.order", orderCreateVo);
        // 该队列用于检测超时订单
        rabbitTemplate.convertAndSend("order-event-exchange", "order.delay", orderCreateVo);

        // 制作库存工单
        LockOrderWareTo lockOrderWareTo = new LockOrderWareTo();
        lockOrderWareTo.setOrderSn(orderCreateVo.getOrderEntity().getOrderSn());  // 未拆分总订单的sn
        // 保存总订单中所有商品小项的购买详情，ware模块将根据这些信息依次去锁定每一个商品的库存
        List<LockOrderItemWareTo> lockOrderItemWareToList = orderCreateVo.getOrderItemEntityList().stream().map(orderItemEntity -> {
            LockOrderItemWareTo lockOrderItemWareTo = new LockOrderItemWareTo();
            lockOrderItemWareTo.setSkuId(orderItemEntity.getSkuId());
            lockOrderItemWareTo.setCount(orderItemEntity.getSkuQuantity());
            lockOrderItemWareTo.setTitle(orderItemEntity.getSkuName());
            return lockOrderItemWareTo;
        }).collect(Collectors.toList());
        lockOrderWareTo.setLockList(lockOrderItemWareToList);  // 订单中所有商品小项的信息，放到wareSkuLockVo中
        // 该队列将用于处理库存工单
        rabbitTemplate.convertAndSend("stock-event-exchange", "stock.seckill.stock", lockOrderWareTo);

        return orderCreateVo;
    }

    /**
     * <b>已弃用</b>
     * 处理秒杀队列中的秒杀订单
     *
     * @param secKillOrderTo 消息队列中的秒杀信息
     */
    @Override
    public void secKillOrder(SecKillOrderTo secKillOrderTo) {
        // 为了简单这里就不真的创建订单了
        long s1 = System.currentTimeMillis();
        System.out.println("后台创建订单==>" + secKillOrderTo.getOrderSn());
        // 延迟模拟订单处理
        int i = 5000000;
        while (i > 0) {
            i--;
        }
        long s2 = System.currentTimeMillis();
        System.out.println("创建订单耗时==>" + (s2 - s1));
    }

    /**
     * <b>已弃用</b>
     * 新秒杀逻辑下，监听到消息队列中的 未支付状态的新秒杀订单，将其入库
     *
     * @param orderCreateVo 未支付状态的秒杀订单
     */
    @Override
    @Transactional
    public void saveSeckillNewOrder(OrderCreateVo orderCreateVo) {
        LoginSuccessTo loginSuccessTo = JSON.parseObject((String) ThreadLocalContextHolder.get(AuthConstant.LOGIN_USER_THREADLOCAL_NAME), LoginSuccessTo.class);
        // 需要使用分布式锁，防止多个队列之间处理速度不一致导致的分布式问题。
        // 使用 seckill:order:lock:orderSn:userId 做锁
        RLock order_user_lock = redissonClient.getLock(
                "seckill:order:lock:" + orderCreateVo.getOrderEntity().getOrderSn() + ":" + loginSuccessTo.getId());
        try {
            // 锁的粒度很低，专门的用户专门的订单专门的锁，所以使用tryLock且等待时间为3，不会造成阻塞且超时就意味着另外抢占锁的业务异常
            boolean tryLock = order_user_lock.tryLock(3, TimeUnit.SECONDS);
            // 获取不到锁直接抛出异常就可以了，异常传递到RabbitMQ监听器中，会进行失败回调，消息将被重新放回到消息队列中
            if (!tryLock) {
                throw new RuntimeException("订单被锁定，无法操作订单，请检查链路中其他业务是否异常占用订单锁");
            }
            saveUnpayOrder(null, orderCreateVo);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}